// 12.1 动态内存与智能指针
/**
 * 在C++中，动态内存的管理是通过一对运算符来完成的：
 * 1. new，在动态内存中为对象分配空间并返回一个指向该对象的指针，我们可以选择对对象进行初始化；
 * 2. delete，接受一个动态对象的指针，销毁该对象，并释放与之关联的内存。
 * 新标准库提供的这两种智能指针的区别在于管理底层指针的方式：
 * 1. shared_ptr允许多个指针指向同一个对象；
 * 2. unique_ptr则“独占”所指向的对象。
 * 标准库还定义了一个名为weak_ptr的伴随类，它是一种弱引用，指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;

int main()
{
    // 12.1.2 直接管理内存
    // C++语言定义了两个运算符来分配和释放动态内存。
    // 运算符new分配内存，delete释放new分配的内存。相对于智能指针，使用这两个运算符管理内存非常容易出错

    // 使用new动态分配和初始化对象
    // 在自由空间分配的内存是无名的，因此new无法为其分配的对象命名，而是返回一个指向该对象的指针：[插图]
    int *pi = new int; // pi指向一个动态分配的、未初始化的无名对象
    // 默认情况下，动态分配的对象是默认初始化的（参见2.2.1节，第40页），这意味着内置类型或组合类型的对象的值将是未定义的，
    // 而类类型对象将用默认构造函数进行初始化：[插图]
    string *ps = new string; // 初始化为空string
    // 我们可以使用直接初始化方式（参见3.2.1节，第76页）来初始化一个动态分配的对象。
    // 我们可以使用传统的构造方式（使用圆括号），在新标准下，也可以使用列表初始化（使用花括号）：[插图]
    int *pi1 = new int(1024);          // pi1指向的对象的值为1024
    string *ps1 = new string(10, '9'); // *ps1的值为9999999999
    vector<int> *pv1 = new vector<int>{01, 2, 3, 4, 5, 6, 7, 8, 9};
    // 也可以对动态分配的对象进行值初始化（参见3.3.1节，第88页），只需在类型名之后跟一对空括号即可：[插图]
    string *ps2 = new string;   // 默认初始化为空string
    string *ps3 = new string(); // 值初始化为空string
    int *pi2 = new int;         // 默认初始化，*pi2的值未定义
    int *pi3 = new int();       // 值初始化为0，*pi3为0
    // 出于与变量初始化相同的原因，对动态分配的对象进行初始化通常是个好主意。

    // 动态分配的const对象
    // 用new分配const对象是合法的：
    const int *pci = new const int(1024); // 分配并初始化一个const int
    const string *pcs = new const string; // 分配并默认初始化一个const的空string
    // 于分配的对象是const的，new返回的指针是一个指向const的指针（参见2.4.2节，第56页）。

    // 内存耗尽
    // 默认情况下，如果new不能分配所要求的内存空间，它会抛出一个类型为bad_alloc（参见5.6节，第173页）的异常。
    // 我们可以改变使用new的方式来阻止它抛出异常：[插图]
    int *pi4 = new int;           // 如果分配失败，new抛出std::bad_alloc异常
    int *pi5 = new (nothrow) int; // 如果分配失败，new返回一个空指针
    //我们称这种形式的new为定位new（placement new）

    // 释放动态内存
    // 为了防止内存耗尽，在动态内存使用完毕后，必须将其归还给系统。我们通过delete表达式（delete expression）来将动态内存归还给系统。
    int *p = new int;
    delete p; // p必须指向一个动态分配的对象或是一个空指针

    // 指针值和delete
    // 释放一块并非new分配的内存，或者将相同的指针值释放多次，其行为是未定义的：[插图]
    int i, *pi6 = &i, *pi7 = nullptr;
    double *pd = new double(33), *pd2 = pd;
    // delete i;   // 错误，i不是一个指针
    delete pi6; // 未定义，pi6指向一个局部变量
    delete pd;  // 正确
    delete pd2; // 未定义，pd2指向的内存已被释放掉了
    delete pi7; // 正确，释放一个空指针总是没有错误的
    // 虽然一个const对象的值不能被改变，但它本身是可以被销毁的。
    // 如同任何其他动态对象一样，想要释放一个const动态对象，只要delete指向它的指针即可：[插图]
    delete pci; // 正确，释放一个const对象

    // 动态对象的生存期直到被释放为止
    // 返回指向动态内存的指针（而不是智能指针）的函数给其调用者增加了一个额外负担——调用者必须记得释放内存

    // 小心：动态内存的管理非常容易出错
    // 坚持只使用智能指针，就可以避免所有这些问题。对于一块内存，只有在没有任何智能指针指向它的情况下，智能指针才会自动释放它。

    // delete之后重置指针值
    // 如果我们需要保留指针，可以在delete之后将nullptr赋予指针，这样就清楚地指出指针不指向任何对象。

    // 在实际系统中，查找指向相同内存的所有指针是异常困难的。

    return 0;
}